//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Blackboard.Common
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Runtime.Serialization.Formatters.Binary;
    using System.IO;
    using System.Xml.Serialization;
    using System.Xml.Schema;
    using System.Xml;
    using System.Xml.Linq;
    using System.Linq;
    using System.Diagnostics;

    /// <summary>
    /// Represents a dictionary of key-value pairs (both key and value are strings) which comprise a blackboard message.
    /// </summary>
    [Serializable]
    public class BlackboardMessage : Dictionary<string, string>, IXmlSerializable
    {
        /// <summary>
        /// Name of the Item token.
        /// </summary>
        private static readonly string ItemToken = "Item";

        /// <summary>
        /// Name of the Key token.
        /// </summary>
        private static readonly string KeyToken = "Key";

        /// <summary>
        /// Name of the Value token.
        /// </summary>
        private static readonly string ValueToken = "Value";

        /// <summary>
        /// Name of the string token.
        /// </summary>
        private static readonly string StringToken = "string";

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        public BlackboardMessage()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="capacity">The capacity.</param>
        public BlackboardMessage(int capacity)
            : base(capacity)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="comparer">The comparer.</param>
        public BlackboardMessage(IEqualityComparer<string> comparer)
            : base(comparer)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        public BlackboardMessage(IDictionary<string, string> dictionary)
            : base(dictionary)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="capacity">The capacity.</param>
        /// <param name="comparer">The comparer.</param>
        public BlackboardMessage(int capacity, IEqualityComparer<string> comparer)
            : base(capacity, comparer)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="dictionary">The dictionary.</param>
        /// <param name="comparer">The comparer.</param>
        public BlackboardMessage(IDictionary<string, string> dictionary, IEqualityComparer<string> comparer)
            : base(dictionary, comparer)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BlackboardMessage"/> class.
        /// </summary>
        /// <param name="info">A <see cref="T:System.Runtime.Serialization.SerializationInfo"/> object containing the information required to serialize the <see cref="T:System.Collections.Generic.Dictionary`2"/>.</param>
        /// <param name="context">A <see cref="T:System.Runtime.Serialization.StreamingContext"/> structure containing the source and destination of the serialized stream associated with the <see cref="T:System.Collections.Generic.Dictionary`2"/>.</param>
        protected BlackboardMessage(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }

        #region Public Methods

        /// <summary>
        /// This method calculates the size of the message either in the binary
        /// format or as lengths of individual string members depending on the 
        /// setting the settings file
        /// </summary>
        /// <returns></returns>
        public long GetMessageSize()
        {
            long msgSize = 0;
            SizeMethod sizeMethod = BlackBoardSettings.SerializationSettings.MessageSizeMethod;
            if (SizeMethod.Binary == sizeMethod)
            {
                BinaryFormatter objFormatter = new BinaryFormatter();
                using (MemoryStream memStream = new MemoryStream())
                {
                    foreach (var item in this)
                    {
                        objFormatter.Serialize(memStream, item.Key);
                        objFormatter.Serialize(memStream, item.Value);
                    }
                    msgSize = memStream.Length;
                }
            }
            else if (SizeMethod.String == sizeMethod)
            {
                foreach (var item in this)
                {
                    msgSize += item.Key.Length;
                    msgSize += item.Value.Length;
                }
            }
            return msgSize;
        }

        #endregion

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.AppendLine(String.Format(CultureInfo.CurrentUICulture, "BlackboardMessage, Count={0}", this.Count));
            foreach (var item in this)
            {
                sb.AppendLine(String.Format(CultureInfo.CurrentUICulture, " Concept='{0}', Value='{1}'", item.Key, item.Value));
            }

            return sb.ToString();
        }

        /// <summary>
        /// This method is reserved and should not be used. When implementing the IXmlSerializable 
        /// interface, you should return null (Nothing in Visual Basic) from this method, and instead, 
        /// if specifying a custom schema is required, apply the 
        /// <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the 
        /// object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> 
        /// method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public XmlSchema GetSchema()
        {
            return null;
        }

        /// <summary>
        /// Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized.</param>
        public void ReadXml(XmlReader reader)
        {
            XmlDocument xDom = new XmlDocument();
            xDom.Load(reader);
            foreach (XmlNode node in xDom.ChildNodes[0].ChildNodes)
            {
                this[node[KeyToken].SelectSingleNode(StringToken).InnerXml] = node[ValueToken].SelectSingleNode(StringToken).InnerXml;
            }
        }

        /// <summary>
        /// Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized.</param>
        public void WriteXml(XmlWriter writer)
        {
            XmlSerializer keySerializer = new XmlSerializer(typeof(string));
            XmlSerializer valueSerializer = new XmlSerializer(typeof(string));

            foreach (string key in this.Keys)
            {
                writer.WriteStartElement(ItemToken);
                writer.WriteStartElement(KeyToken);
                keySerializer.Serialize(writer, key);
                writer.WriteEndElement();
                writer.WriteStartElement(ValueToken);
                string value = this[key];
                valueSerializer.Serialize(writer, value);
                writer.WriteEndElement();
                writer.WriteEndElement();
            }
        }
    }
}
